using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using org.neo4j.graphdb;
using Kernel;

//
// * Copyright (c) 2002-2010 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel
{


	using TransactionManager = javax.transaction.TransactionManager;

	using Node = org.neo4j.graphdb.Node;
	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using Transaction = org.neo4j.graphdb.Transaction;
	using ShellNotAvailableException = org.neo4j.kernel.ShellService.ShellNotAvailableException;
	using NodeManager = org.neo4j.kernel.impl.core.NodeManager;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;

	internal class EmbeddedGraphDbImpl
	{
		private static Logger log = Logger.GetLogger(typeof(EmbeddedGraphDbImpl).Name);
		private ShellService shellService;
		private Transaction placeboTransaction = null;
		private readonly GraphDbInstance graphDbInstance;
		private readonly IGraphDatabaseService graphDbService;
		private readonly NodeManager nodeManager;
		private readonly string storeDir;

///    
///     <summary> * Creates an embedded <seealso cref="IGraphDatabaseService"/> with a store located in
///     * <code>storeDir</code>, which will be created if it doesn't already exist.
///     *  </summary>
///     * <param name="storeDir"> the store directory for the Neo4j db files </param>
///     
		public EmbeddedGraphDbImpl(string storeDir, IGraphDatabaseService graphDbService)
		{
			this.storeDir = storeDir;
			graphDbInstance = new GraphDbInstance(storeDir, true);
			graphDbInstance.Start();
			nodeManager = graphDbInstance.getConfig().getNeoModule().getNodeManager();
			this.graphDbService = graphDbService;
		}

///    
///     <summary> * A non-standard way of creating an embedded <seealso cref="GraphDatabaseService"/>
///     * with a set of configuration parameters. Will most likely be removed in
///     * future releases.
///     *  </summary>
///     * <param name="storeDir"> the store directory for the db files </param>
///     * <param name="params"> configuration parameters </param>
///     
		public EmbeddedGraphDbImpl(string storeDir, Dictionary<string, string> @params, IGraphDatabaseService graphDbService)
		{
			this.storeDir = storeDir;
			graphDbInstance = new GraphDbInstance(storeDir, true);
			graphDbInstance.Start(@params);
			nodeManager = graphDbInstance.getConfig().getNeoModule().getNodeManager();
			this.graphDbService = graphDbService;
		}

///    
///     <summary> * A non-standard Convenience method that loads a standard property file and
///     * converts it into a generic <Code>Dictionary<String,String></CODE>. Will most
///     * likely be removed in future releases.
///     *  </summary>
///     * <param name="file"> the property file to load </param>
///     * <returns> a map containing the properties from the file </returns>
///     * <exception cref="IllegalArgumentException"> if file does not exist </exception>
///     
		public static Dictionary<string, string> LoadConfigurations(string file)
		{
			Properties props = new Properties();
			try
			{
				FileInputStream stream = new FileInputStream(new File(file));
				try
				{
					props.load(stream);
				}
				finally
				{
					stream.Close();
				}
			}
			catch (Exception e)
			{
				throw new IllegalArgumentException("Unable to load " + file, e);
			}
			Set<KeyValuePair<object, object>> entries = props.entrySet();
			Dictionary<string, string> stringProps = new Dictionary<string, string>();
			foreach (KeyValuePair<object, object> entry in entries)
			{
				string key = (string) entry.Key;
				string @value = (string) entry.Value;
				stringProps.put(key, @value);
			}
			return stringProps;
		}

		public virtual Node CreateNode()
		{
			return nodeManager.createNode();
		}

		public virtual Node GetNodeById(long id)
		{
			if (id < 0 || id > int.MaxValue * 2l)
			{
				throw new NotFoundException("Node[" + id + "]");
			}
			return nodeManager.getNodeById((int) id);
		}

		public virtual Relationship GetRelationshipById(long id)
		{
			if (id < 0 || id > int.MaxValue * 2l)
			{
				throw new NotFoundException("Relationship[" + id + "]");
			}
			return nodeManager.getRelationshipById((int) id);
		}

		public virtual Node GetReferenceNode()
		{
			return nodeManager.getReferenceNode();
		}

		public virtual void shutdown()
		{
			if (this.shellService != null)
			{
				try
				{
					this.shellService.shutdown();
				}
				catch (System.Exception t)
				{
					log.Warning("Error shutting down shell server: " + t);
				}
			}
			graphDbInstance.shutdown();
		}

		public virtual bool EnableRemoteShell()
		{
			return this.enableRemoteShell(null);
		}

		public virtual bool EnableRemoteShell(Dictionary<string, object > initialProperties)
		{
			if (shellService != null)
			{
				throw new IllegalStateException("Shell already enabled");
			}

			Dictionary<string, object> properties = initialProperties;
			if (properties == null)
			{
				properties = Collections.emptyDictionary();
			}
			try
			{
				shellService = new ShellService(this.graphDbService, properties);
				return true;
			}
			catch (RemoteException e)
			{
				throw new IllegalStateException("Can't start remote Neo4j shell", e);
			}
			catch (ShellNotAvailableException e)
			{
				log.info("Shell library not available. Neo4j shell not " + "started. Please add the Neo4j shell jar to the classpath.");
				e.printStackTrace();
				return false;
			}
		}

		public virtual IEnumerable<RelationshipType> getRelationshipTypes()
		{
			return graphDbInstance.GetRelationshipTypes();
		}

///    
///     * <exception cref="TransactionFailureException"> if unable to start transaction </exception>
///     
		public virtual Transaction BeginTx()
		{
			if (graphDbInstance.transactionRunning())
			{
				if (placeboTransaction == null)
				{
					placeboTransaction = new PlaceboTransaction(graphDbInstance.getTransactionManager());
				}
				return placeboTransaction;
			}
			TransactionManager txManager = graphDbInstance.getTransactionManager();
			try
			{
				txManager.Begin();
			}
			catch (Exception e)
			{
				throw new TransactionFailureException("Unable to begin transaction", e);
			}
			return new TransactionImpl(txManager);
		}

		private class PlaceboTransaction : Transaction
		{
			private readonly TransactionManager transactionManager;

			internal PlaceboTransaction(TransactionManager transactionManager)
			{
			// we should override all so null is ok
				this.transactionManager = transactionManager;
			}

			public virtual void Failure()
			{
				try
				{
					transactionManager.GetTransaction().setRollbackOnly();
				}
				catch (Exception e)
				{
					throw new TransactionFailureException("Failed to mark transaction as rollback only.", e);
				}
			}

			public virtual void Success()
			{
			}

			public virtual void Finish()
			{
			}
		}

///    
///     <summary> * Returns a non-standard configuration object. Will most likely be removed
///     * in future releases.
///     *  </summary>
///     * <returns> a configuration object </returns>
///     
		public virtual Config GetConfig()
		{
			return graphDbInstance.getConfig();
		}

		private class TransactionImpl : Transaction
		{
			private bool success = false;

			private readonly TransactionManager transactionManager;

			internal TransactionImpl(TransactionManager transactionManager)
			{
				this.transactionManager = transactionManager;
			}

			public virtual void Failure()
			{
				this.success = false;
				try
				{
					transactionManager.GetTransaction().setRollbackOnly();
				}
				catch (Exception e)
				{
					throw new TransactionFailureException("Failed to mark transaction as rollback only.", e);
				}
			}

			public virtual void Success()
			{
				success = true;
			}

			public virtual void Finish()
			{
				try
				{
					if (success)
					{
						if (transactionManager.GetTransaction() != null)
						{
							transactionManager.GetTransaction().commit();
						}
					}
					else
					{
						if (transactionManager.GetTransaction() != null)
						{
							transactionManager.GetTransaction().rollback();
						}
					}
				}
				catch (Exception e)
				{
					if (success)
					{
						throw new TransactionFailureException("Unable to commit transaction", e);
					}
					else
					{
						throw new TransactionFailureException("Unable to rollback transaction", e);
					}
				}
			}
		}

		public override string ToString()
		{
			return base.ToString() + " [" + storeDir + "]";
		}

		public virtual string getStoreDir()
		{
			return storeDir;
		}

		public virtual IEnumerable<Node> GetAllNodes()
		{
			return new IEnumerable<Node>()
			{
				public IEnumerable<Node> iterator()
				{
					long highId = (nodeManager.getHighestPossibleIdInUse(typeof(Node)) & 0xFFFFFFFFL);
					return new AllNodesIterator(highId);
				}
			}
		}

	// TODO: temporary all nodes getter, fix this with better implementation
	// (no NotFoundException to control flow)
		private class AllNodesIterator : IEnumerator<Node>
		{
			private readonly long highId;
			private long currentNodeId = 0;
			private Node currentNode = null;

			internal AllNodesIterator(long highId)
			{
				this.highId = highId;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			public virtual bool HasNext()
			{
				while (currentNode == null && currentNodeId <= highId)
				{
					try
					{
						currentNode = GetNodeById(currentNodeId++);
					}
					catch (NotFoundException e)
					{
					// ok we try next
					}
				}
				return currentNode != null;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			public virtual Node Next()
			{
				if (!HasNext())
				{
					throw new NoSuchElementException();
				}

				Node nextNode = currentNode;
				currentNode = null;
				return nextNode;
			}

			public virtual void Remove()
			{
				throw new UnsupportedOperationException();
			}
		}
	}
}